#include <string>
#include <cerrno>
#include <cstring>
#include <cassert>
#include "../src/Log.hpp"
#include "../src/Socket.hpp"
#include "Utils.hpp"

#include <signal.h>

/**
 * @brief 测试短连接1
 * 创建一个客户端，向服务器发送一个短连接的通信请求，检查服务端该连接的生命周期是否正常
 * 
 * @param sock 通信套接字
 */
void Client1(Socket& sock)
{
    std::string req = "GET /hello HTTP/1.1\r\nContent-Length: 0\r\n\r\n";
    // DBG_LOG("seqStr length: %ld", req.size());
    while(true)
    {
        ssize_t n = sock.Send(req.c_str(), req.size());
        assert(n != -1);
        INF_LOG("client send %ld size data to server", n);
        char buffer[1024] = { 0 };

        n = sock.Recv(buffer, 1023);
        if(n < 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED");
            return;
        }
        if(n == 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED BECAUSE SOCKET HAS BEEN CLOSED");
            return;
        }
        INF_LOG("client recv:[\r\n%s\r\n]\r\n", buffer);
        sleep(3);
    }
}

/**
 * @brief 测试长连接
 * 创建一个客户端，向服务器发送一个长连接的通信请求，只发送一次，然后等待连接被释放，检查服务端该连接的生命周期是否正常
 * 
 * @param sock 通信套接字
 */
void Client2(Socket& sock)
{
    std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
    // DBG_LOG("seqStr length: %ld", req.size());
    while(true)
    {
        /* 服务端关闭和客户端的连接后，客户端第一次向服务端发送数据，OS可能不会立即检测到连接已关闭，send函数就仍会
        返回发送的字节数，因为send函数只是将要发送的数据拷贝到内核的发送缓冲区，并不保证数据能成功的发送到高对端。
        后续再次调用send或recv(本代码是在send后就紧接着调用了recv，所以recv会报错)，OS就会检测到连接已断开，send或recv就会返回-1，
        并设置错误码为EPIPE或ECONNRESET。
        EPIPE表示向一个已经关闭的连接写入数据。在TCP中，当服务端主动关闭连接后，客户端继续向该连接发送数据，就会触发EPIPE错误。
        ECONNRESET表示连接被对端重置。当服务端异常关闭连接（例如崩溃）时，客户端在后续操作中会收到ECONNRESET错误 */
        ssize_t n = sock.Send(req.c_str(), req.size());
        assert(n != -1);
        INF_LOG("client send %ld size data to server", n);

        char buffer[1024] = { 0 };

        n = sock.Recv(buffer, 1023);
        if(n < 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED");
            return;
        }
        if(n == 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED BECAUSE SOCKET HAS BEEN CLOSED");
            return;
        }
        INF_LOG("client recv:[\r\n%s\r\n]\r\n", buffer);
        sleep(15);
    }
}

/**
 * @brief 测试上下文
 * 创建一个客户端，将一个请求分多次发给服务器，看服务器的响应是否正常
 * 
 * @param sock 通信套接字
 */
void Client3(Socket& sock)
{
    std::string req1 = "GET /hello HTTP/1.1\r\n";
    std::string req2 = "Content-Length: 10";
    std::string req3 = "\r\n\r\nhello";
    std::string req4 = "world";
    // std::string req1 = "GET /hello HTTP/1.1\r\nConection-keep-alive\r\nContent-Length: 10\r\n\r\nhello";
    // std::string req1 = "GET /hello HTTP/1.1\r\nContent-Length: 10\r\n\r\nhello";
    // std::string req2 = "world";

    while(true)
    {
        assert(sock.Send(req1.c_str(), req1.size()) != -1);
        assert(sock.Send(req2.c_str(), req2.size()) != -1);
        assert(sock.Send(req3.c_str(), req3.size()) != -1);
        assert(sock.Send(req4.c_str(), req4.size()) != -1);
        
        char buffer[1024] = { 0 };

        ssize_t n = sock.Recv(buffer, 1023);
        if(n < 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED");
            return;
        }
        if(n == 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED BECAUSE SOCKET HAS BEEN CLOSED");
            return;
        }
        INF_LOG("client recv:[\r\n%s\r\n]\r\n", buffer);
        sleep(15);
    }
}

/**
 * @brief 不完整请求1
 * 创建一个客户端，向服务器发送一条不完整请求，然后什么都不做
 * 
 * @param sock 
 */
void Client4(Socket& sock)
{
    // std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\n";
    std::string req = "GET /hello HTTP/1.1\r\nContent-Length: 100\r\n\r\n";

    while(true)
    {
        ssize_t n = sock.Send(req.c_str(), req.size());
        assert(n != -1);
        INF_LOG("client send %ld size data to server", n);

        char buffer[1024] = { 0 };
        n = sock.Recv(buffer, 1023);
        if(n < 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED");
            return;
        }
        if(n == 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED BECAUSE SOCKET HAS BEEN CLOSED");
            return;
        }
        INF_LOG("client recv:[\r\n%s\r\n]\r\n", buffer);
        // sleep(15);
    }
}

/**
 * @brief 不完整请求2
 * 创建一个客户端，向服务器发送多条请求，告诉服务器正文长度是100字节，但实际正文长度不足100字节，
 * 服务器就会把后面的请求当作前面请求的正文进行处理
 * 
 * @param sock 
 */
void Client5(Socket& sock)
{
    // std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 100\r\n\r\ndata";
    std::string req = "GET /hello HTTP/1.1\r\nContent-Length: 100\r\n\r\ndata";
    while(true)
    {
        assert(sock.Send(req.c_str(), req.size()) != -1);
        assert(sock.Send(req.c_str(), req.size()) != -1);
        assert(sock.Send(req.c_str(), req.size()) != -1);// exit code 141 SIGPIPE

        char buffer[1024] = { 0 };
        ssize_t n = sock.Recv(buffer, 1023);
        if(n < 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED");
            return;
        }
        if(n == 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED BECAUSE SOCKET HAS BEEN CLOSED");
            return;
        }
        INF_LOG("client recv:[\r\n%s\r\n]\r\n", buffer);
        sleep(5);
    }
}

/**
 * @brief 当服务器在一次业务处理中花费了很长的时间(超过非活跃连接释放的超时时间)，就可能导致其他的连接被连累导致超时，
 * 这些被连累超时的连接就可能被释放。
 * 假设目前有4个描述符sock1,sock2,sock3,sock4，都已就绪，如果处理sock1上的事件的事件过长，导致sock2，sock3，sock4超时。
 * 1. 若sock2~4都是通信连接，那么当sock1上的事件处理完成后，sock2~4都超时，但直接执行相应的定时任务即可，然后将连接释放，
 * 并不会影响什么
 * 2. 但若sock2是定时器描述符，这时候就定时器超时，就可能会将sock3~4释放掉，关闭sock3~4的连接，此后在处理处理sock3~4上的事件时(如刷新活跃度)
 * 就会造成非法访问，所以，这里关闭连接不能直接关闭，而是将“关闭连接”当作任务押入到线程的任务池中
 * 
 * 创建多个子进程，由子进程向服务器发起通信长连接，并循环发送数据
 * 
 * @param sock 
 */
void Client6(Socket& sock)
{
    sock.Close();

    signal(SIGCHLD, SIG_IGN);
    for(int i = 0; i < 10; ++i)
    {
        pid_t pid = fork();
        if(pid < 0)
        {
            ERR_LOG("FORK FAILED");
            return;
        }
        if(pid == 0)
        {
            Socket sock;
            assert(sock.CreateClient("127.0.0.1", 9000));
            INF_LOG("CLIENT SOCKET %d CREATED, %d", sock.Fd(), i + 1);

            std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
            while(true)
            {
                assert(sock.Send(req.c_str(), req.size()) != -1);

                char buffer[1024] = { 0 };
                if(sock.Recv(buffer, 1023) < 0)
                {
                    INF_LOG("CONNECTION HAS BEEN CLOSED");
                    break;
                }
                DBG_LOG("client recv:[\r\n%s\r\n]\r\n", buffer);
            }
            exit(0);
        }
    }
    while(true){}
}

/**
 * @brief 创建一个客户端，一次向服务器发送多个请求，检查这多个请求是否都被正常处理
 * 
 * @param sock 
 */
void Client7(Socket& sock)
{
    std::string req = "GET /hello HTTP/1.1\r\nContent-Length: 0\r\n\r\n";
    req += "GET /hello HTTP/1.1\r\nContent-Length: 0\r\n\r\n";
    req += "GET /hello HTTP/1.1\r\nContent-Length: 0\r\n\r\n";
    req += "GET /hello HTTP/1.1\r\nContent-Length: 0\r\n\r\n";

    assert(sock.Send(req.c_str(), req.size()) != -1);

    DBG_LOG("CLIENT SEND DATA %ld", req.size());

    char buffer[1024] = { 0 };
    if(sock.Recv(buffer, 1023) < 0)
    {
        INF_LOG("CONNECTION HAS BEEN CLOSED");
        return;
    }
    INF_LOG("client recv:[\r\n%s\r\n]\r\n", buffer);
    sleep(3);
}

/**
 * @brief 给服务器上传一个大文件，检查服务器上保存的文件和我们本地的文件是否一致（使用MD5）
 * 在当前路径创建一个1G大小的文件：dd if=/dev/zero of=uploadFile.txt bs=1G count=1
 *      if：输入流，从/dev/zero设备文件输入
 *      of：输出流，输出到uploadFile.txt文件，没有就创建
 *      bs：块大小，就是输出文件的大小
 *      count：块个数
 * 设备文件/devzero中的数据都是0，为了保证数据的不同和清晰，在创建的文件uploadFile.txt内容末尾追加上一个"upload"
 * 
 * bug：
 * 客户端向服务端发送请求后，通信套接字服务端的接收缓冲区有数据，触发可读事件回调，服务器从套接字中读取65535字节数据到
 * Connection的接收缓冲区inbuffer，inbuffer初始大小为1024字节，所以会扩容。
 * 然后调用信息处理回调，在回调函数中取出inbuffer中的数据进行解析，并发送。
 * 每次接收完数据，都会调用回调函数将数据解析完并发送，所以只需要第一次扩容一次。
 * 即多次接收，多次发送。但测试发现，本代码是多次接收，一次发送
 * 
 * bug分析：经测试+检查，是Buffer缓冲区扩容机制出现了错误，已修正
 * 
 * 该代码：为了保证当前在系统内存不充足的情况下上传1G大文件成功，这里让子进程读取文件按并发送，发送完立即结束子进程。
 * 但其实和Client10()是一样的效果，因为套接字的缓冲区是有限的，不足以一次存储1G大小的数据，所以必定是客户端不断向套接字
 * 中存放数据，而服务端不断从套接字中取出数据。所以客户端这个一次发送1G大小的数据，必定是一个阶段性的任务，是持续多次发送的。
 * 
 * @param sock
 */
void Client8(Socket& sock)
{
    pid_t pid = fork();
    if(pid < 0)
    {
        ERR_LOG("FORK FAILED");
        exit(-1);
    }
    if(pid == 0)
    {
        std::string req = "PUT /uploadFile.txt HTTP/1.1\r\n";// 请求行
        req += "Connection: keep-alive\r\n";// 请求头部
        std::string body;// 请求正文
        assert(Utils::ReadFile("uploadFile.txt", &body));
        // DBG_LOG("REQ BODY SIZE: %ld", body.size());
        req += "Content-Length: " + std::to_string(body.size()) + "\r\n";
        req += "\r\n";// 请求空行
        // DBG_LOG("REQ HEAD: [%s]", req.c_str());
        // req += body;
        // body.~basic_string();
        // DBG_LOG("REQ SIZE: %ld [%s]", req.size(), req.c_str());
        INF_LOG("REQ SIZE: %ld", req.size());
        // 为了减少系统开销，这里就不将body追加到req后了
        assert(sock.Send(req.c_str(), req.size()) != -1);
        assert(sock.Send(body.c_str(), body.size()) != -1);

        char buffer[1024] = { 0 };
        if(sock.Recv(buffer, 1023) < 0)
        {
            INF_LOG("CONNECTION HAS BEEN CLOSED");
            return;
        }
        INF_LOG("CLIENT RECV:[\r\n%s\r\n]\r\n", buffer);

        exit(0);
    }

    while(true){}
}

/**
 * @brief 长连接测试：客户端向服务端发送一次请求，然后结束
 * 
 * @param sock 
 */
void Client9(Socket& sock)
{
    std::string req = "GET /hello HTTP/1.1\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n";
    ssize_t n = sock.Send(req.c_str(), req.size());
    assert(n != -1);
    INF_LOG("client send %ld size data to server", n);
    char buffer[1024] = { 0 };
    if(sock.Recv(buffer, 1023) < 0)
    {
        INF_LOG("CONNECTION HAS BEEN CLOSED");
        return;
    }
    INF_LOG("client recv:[\r\n%s\r\n]\r\n", buffer);
    sleep(15);
}
/**
 * @brief Client8的另一个版本，不创建子进程
 * 
 */
void Client10()
{
    Socket sock;
    assert(sock.CreateClient("127.0.0.1", 9000));

    INF_LOG("CLIENT SOCKET %d CREATED", sock.Fd());

    std::string req = "PUT /uploadFile.txt HTTP/1.1\r\n";// 请求行
    req += "Connection: keep-alive\r\n";// 请求头部
    std::string body;// 请求正文
    assert(Utils::ReadFile("uploadFile.txt", &body));
    req += "Content-Length: " + std::to_string(body.size()) + "\r\n";
    req += "\r\n";// 请求空行
    INF_LOG("REQ SIZE: %ld", req.size());
    // 为了减少系统开销，这里就不将body追加到req后了
    assert(sock.Send(req.c_str(), req.size()) != -1);
    assert(sock.Send(body.c_str(), body.size()) != -1);

    char buffer[1024] = { 0 };
    assert(sock.Recv(buffer, 1023));
    INF_LOG("CLIENT RECV: [%s]", buffer);
    while(true);
}

void Test()
{
    pid_t pid = fork();
    if(pid < 0)
    {
        ERR_LOG("FORK FAILED");
        exit(-1);
    }
    if(pid == 0)
    {
        std::string req = "PUT /uploadFile.txt HTTP/1.1\r\n";// 请求行
        req += "Connection: keep-alive\r\n";// 请求头部
        std::string body;// 请求正文
        assert(Utils::ReadFile("uploadFile.txt", &body));
        // DBG_LOG("REQ BODY SIZE: %ld", body.size());
        req += "Content-Length: " + std::to_string(body.size()) + "\r\n";
        req += "\r\n";// 请求空行
        // DBG_LOG("REQ HEAD: [%s]", req.c_str());
        req += body;
        body.~basic_string();
        exit(0);
    }
    while(true){}
}

int main()
{
#if 1
    Socket sock;
    assert(sock.CreateClient("127.0.0.1", 9000));

    INF_LOG("CLIENT SOCKET %d CREATED", sock.Fd());

    // Client1(sock);
    // Client2(sock);
    // Client3(sock);
    // Client4(sock);
    // Client5(sock);
    // Client6(sock);
    // Client7(sock);
    Client8(sock);
    // Client9(sock);
    // Client10();
#else
    // Test();
    Client10();
#endif
    sleep(2);

    return 0;
}